Imports System.Configuration
Imports System.Configuration.ConfigurationManager
Imports System.Web.Configuration
Imports System.Web.HttpContext

<CLSCompliant(True)> _
Public Class CConfigBase

    'Standard Connection String
    Public Shared Function Driver() As String
        Return Config("Driver")
    End Function
    Public Shared Function ConnectionString() As String
        If ConnectionStrings.Count = 0 Then
            Return Config("ConnectionString")
        Else
            Return Config("ConnectionString", ConnectionStrings(0).ConnectionString) 'Use the first connnection string
        End If
    End Function
    Public Shared Function ConnectionString(ByVal name As String) As String
        Dim cs As ConnectionStringSettings = ConnectionStrings(name)
        If IsNothing(cs) Then Return String.Empty
        Return cs.ConnectionString
    End Function

    'Shorter Alternatives
    Public Shared Function SqlExpressPath() As String
        Return Config("SqlExpressPath", Config("SqlExpress"))
    End Function
    Public Shared Function AccessDatabasePath() As String
        Return Config("AccessDatabasePath", Config("AccessDatabase"))
    End Function
    Public Shared Function ExcelDatabasePath() As String
        Return Config("ExcelDatabasePath", Config("ExcelDatabase"))
    End Function
    Public Shared Function SqlCEPath() As String
        Return Config("SqlCEPath", Config("SqlCE"))
    End Function
    Public Shared Function WebSite() As String
        Return Config("WebSite")
    End Function

    'Named connection string (refers to Connection Strings node)
    Public Shared Function ActiveConnectionString() As String
        Return Config("ActiveConnectionString")
    End Function

    Public Shared Function CommandTimeoutSecs() As Integer
		Return ConfigInt("CommandTimeoutSecs", 3 * 60)
	End Function


    'Default Encryption key(s)
    Friend Shared Function FastEncryptionKey() As String
        Return Config("EncryptionKey", WebServicePassword)
    End Function
    Friend Shared ReadOnly Property TripleDesIV() As String
        Get
            Return Config("TripleDesIV", "D08601BA9F91BA88")
        End Get
    End Property
    Friend Shared ReadOnly Property TripleDesKey() As String
        Get
            Return Config("TripleDesKey", "8B682A81B28348D69231136CA376A90C2D3D25CC046B6406")
        End Get
    End Property


    'WebService Driver (and optional Proxy)
    Public Shared Sub CheckPassword(ByVal password As String)
        If Not String.IsNullOrEmpty(CConfigBase.WebServicePassword) Then
            If password <> CConfigBase.WebServicePassword Then Throw New Exception("Invalid webservice password")
        End If
    End Sub
    Private Shared m_password As Byte() = Nothing
    Public Shared ReadOnly Property WebServicePassword() As String
        Get
            Return Config("WebServicePassword", "db75794fe12147539b9596b523d916e87bcbeb06cb35480abca1d98d66b403727b4cd6d4cd344e9eb79cf511ed4f04d9")
        End Get
    End Property
    Public Shared ReadOnly Property UseRawPassword() As Boolean 'Backwards-compat xor encryption (no sha512)
        Get
            Return ConfigBool("UseRawPassword")
        End Get
    End Property
    Public Shared ReadOnly Property WebServicePasswordBytes() As Byte()
        Get
            If IsNothing(m_password) Then
                m_password = CBinary.StringToBytes(WebServicePassword)
                If Not UseRawPassword Then 'Switch to true for backwards-compat
                    m_password = CBinary.Sha512(m_password) 'Stronger password for pack/unpack
                End If
            End If
            Return m_password
        End Get
    End Property
    Friend Shared ReadOnly Property ProxyAddress() As String
        Get
            Return Config("ProxyAddress")
        End Get
    End Property
    Friend Shared ReadOnly Property ProxyUser() As String
        Get
            Return Config("ProxyUser")
        End Get
    End Property
    Friend Shared ReadOnly Property ProxyPassword() As String
        Get
            Return Config("ProxyPassword")
        End Get
    End Property
    Friend Shared ReadOnly Property ProxyDomain() As String
        Get
            Return Config("ProxyDomain")
        End Get
    End Property

    'Default Cache Timeout
    Private Shared m_cacheTimeOut As TimeSpan = TimeSpan.MinValue
    Public Shared ReadOnly Property CacheTimeoutDefault() As TimeSpan
        Get
            If m_cacheTimeOut = TimeSpan.MinValue Then
                m_cacheTimeOut = New TimeSpan(CacheTimeoutHours, CacheTimeoutMinutes, CacheTimeoutSeconds)
            End If
            Return m_cacheTimeOut
        End Get
    End Property
    Private Shared ReadOnly Property CacheTimeoutHours() As Integer
        Get
            Return ConfigInt("CacheTimeoutHours", 3)
        End Get
    End Property
    Private Shared ReadOnly Property CacheTimeoutMinutes() As Integer
        Get
            Return ConfigInt("CacheTimeoutMinutes", 0)
        End Get
    End Property
    Private Shared ReadOnly Property CacheTimeoutSeconds() As Integer
        Get
            Return ConfigInt("CacheTimeoutSeconds", 0)
        End Get
    End Property


	'Write an appSetting
	Public Shared Sub WriteAppSetting(key As String, value As String, overwrite As Boolean)
		If isnothing(value) Then value = String.Empty
		If m_wrote.ContainsKey(key) Then Exit Sub
		Dim v As String = Config(key, Nothing)
		If Not String.IsNullOrEmpty(v) AndAlso v = value AndAlso value.Length > 0 Then Exit Sub
		WriteAppSetting(key, value)
	End Sub
	Public Shared Sub WriteAppSetting(key As String, value As String)
        If m_wrote.ContainsKey(key) Then Exit Sub
        m_wrote(key) = value

        If CApplication.IsWebApplication Then
            Dim webConfig As String = System.Web.HttpContext.Current.Server.MapPath("~/web.config")
            WriteAppSetting(key, value, webConfig)
        Else
            Dim appConfig As String = String.Concat(System.Reflection.Assembly.GetEntryAssembly().Location, ".config")
            WriteAppSetting(key, value, appConfig)
        End If
    End Sub
    Public Shared Sub WriteConnectionString(name As String, value As String)
        Dim key As String = "ConnStr_" & name
        If m_wrote.ContainsKey(key) Then Exit Sub
        m_wrote(key) = value

        If CApplication.IsWebApplication Then
            Dim webConfig As String = System.Web.HttpContext.Current.Server.MapPath("~/web.config")
            WriteConnectionString(key, value, webConfig)
        Else
            Dim appConfig As String = String.Concat(System.Reflection.Assembly.GetEntryAssembly().Location, ".config")
            WriteConnectionString(key, value, appConfig)
        End If
    End Sub
    Public Shared Sub RemoveAppSetting(key As String)
        If CApplication.IsWebApplication Then
            Dim webConfig As String = System.Web.HttpContext.Current.Server.MapPath("~/web.config")
            RemoveAppSetting(key, webConfig)
        Else
            Dim appConfig As String = String.Concat(System.Reflection.Assembly.GetEntryAssembly().Location, ".config")
            RemoveAppSetting(key, appConfig)
        End If
    End Sub
    Public Shared Sub RemoveAppSetting(name As String, path As String)
        If Not IO.File.Exists(path) Then Exit Sub 'Throw New Exception("Config file not found: " & path)

        'Load the xml
        Dim xml As New Xml.XmlDocument
        xml.PreserveWhitespace = True
        xml.LoadXml(IO.File.ReadAllText(path))

        'Find (or create) the node
        Dim root As Xml.XmlNode = xml.DocumentElement
        Dim node As Xml.XmlNode = CXml.ChildNode(root, "appSettings")
        Dim sett As Xml.XmlNode = Nothing
        For Each i As Xml.XmlNode In CXml.ChildNodes(node, "add")
            Dim key As String = CXml.AttributeStr(i, "key")
            If key = name Then
                sett = i
                Exit For
            End If
        Next
        If Not IsNothing(sett) Then
            sett.ParentNode.RemoveChild(sett)
            xml.Save(path)
        End If
    End Sub
    Public Shared Function WriteAppSetting(name As String, value As String, path As String) As Boolean
        If Not IO.File.Exists(path) Then Throw New Exception("Config file not found: " & path)

        'Load the xml
        Dim xml As New Xml.XmlDocument
        xml.PreserveWhitespace = True
        xml.LoadXml(IO.File.ReadAllText(path))

        'Find (or create) the node
        Dim root As Xml.XmlNode = xml.DocumentElement
        Dim node As Xml.XmlNode = CXml.ChildNode(root, "appSettings")
        Dim sett As Xml.XmlNode = Nothing
        Dim all As List(Of Xml.XmlNode) = CXml.ChildNodes(node, "add")
        For Each i As Xml.XmlNode In all
            Dim key As String = CXml.AttributeStr(i, "key")
            Dim val As String = CXml.AttributeStr(i, "value")
            If key = name Then
                If val = value Then Return False 'No change required
                sett = i
                Exit For
            End If
        Next
        If IsNothing(sett) Then
            Dim w As Xml.XmlNode = xml.CreateWhitespace(vbCrLf & "    ")
            If all.Count > 0 Then w = xml.CreateWhitespace("    ")
            node.AppendChild(w)

            sett = CXml.AddNode(node, "add")
            CXml.AttributeSet(sett, "key", name)

            w = xml.CreateWhitespace(vbCrLf & "  ") 'closing tag (appSettings)
            node.AppendChild(w)
        End If

        'Set value and save
        CXml.AttributeSet(sett, "value", value)
        xml.Save(path)
        Return True
    End Function
    Public Shared Function WriteConnectionString(name As String, connectionString As String, path As String) As Boolean
        If Not IO.File.Exists(path) Then Throw New Exception("Config file not found: " & path)

        'Load the xml
        Dim xml As New Xml.XmlDocument
        xml.PreserveWhitespace = True
        xml.LoadXml(IO.File.ReadAllText(path))

        'Find (or create) the node
        Dim root As Xml.XmlNode = xml.DocumentElement
        Dim node As Xml.XmlNode = CXml.ChildNode(root, "connectionStrings")
        Dim sett As Xml.XmlNode = Nothing
        Dim all As List(Of Xml.XmlNode) = CXml.ChildNodes(node, "add")
        For Each i As Xml.XmlNode In all
            Dim key As String = CXml.AttributeStr(i, "name")
            Dim val As String = CXml.AttributeStr(i, "connectionString")
            If key = name Then
                If val = connectionString Then Return False 'No change required
                sett = i
                Exit For
            End If
        Next
        If IsNothing(sett) Then
            Dim w As Xml.XmlNode = xml.CreateWhitespace(vbCrLf & "    ")
            If all.Count > 0 Then w = xml.CreateWhitespace("    ")
            node.AppendChild(w)

            sett = CXml.AddNode(node, "add")
            CXml.AttributeSet(sett, "name", name)
            CXml.AttributeSet(sett, "connectionString", CConfigBase.ConnectionString)

            w = xml.CreateWhitespace(vbCrLf & "  ") 'closing tag (appSettings)
            node.AppendChild(w)
        End If

        'Set value and save
        CXml.AttributeSet(sett, "connectionString", connectionString)
        xml.Save(path)
        Return True
    End Function

    Public Shared Sub RemoveConnectionString(name As String)
        If CApplication.IsWebApplication Then
            Dim webConfig As String = System.Web.HttpContext.Current.Server.MapPath("~/web.config")
            RemoveConnectionString(name, webConfig)
        Else
            Dim appConfig As String = String.Concat(System.Reflection.Assembly.GetEntryAssembly().Location, ".config")
            RemoveConnectionString(name, appConfig)
        End If
    End Sub
    Public Shared Sub RemoveConnectionString(name As String, path As String)
        If Not IO.File.Exists(path) Then Exit Sub 'Throw New Exception("Config file not found: " & path)

        'Load the xml
        Dim xml As New Xml.XmlDocument
        xml.PreserveWhitespace = True
        xml.LoadXml(IO.File.ReadAllText(path))

        'Find (or create) the node
        Dim root As Xml.XmlNode = xml.DocumentElement
        Dim node As Xml.XmlNode = CXml.ChildNode(root, "connectionStrings")
        Dim sett As Xml.XmlNode = Nothing
        For Each i As Xml.XmlNode In CXml.ChildNodes(node, "add")
            Dim key As String = CXml.AttributeStr(i, "name")
            If key = name Then
                sett = i
                Exit For
            End If
        Next
        If Not IsNothing(sett) Then
            sett.ParentNode.RemoveChild(sett)
            xml.Save(path)
        End If
    End Sub

    'Utilities (private)
    Protected Shared Function Config(ByVal key As String) As String
        Try
            Dim s As String = EncryptedSettings(key)
            If Not IsNothing(s) Then Return s

            If m_wrote.TryGetValue(key, s) Then Return s

            s = AppSettings(key)
            If Not IsNothing(s) Then Return s

            Return String.Empty
        Catch ex As Exception
            If IsNothing(ex.InnerException) Then Throw ex
            Throw ex.InnerException
        End Try
    End Function
    Protected Shared Function Config(ByVal key As String, ByVal defaultValue As String) As String
        Config = Config(key)
        If Len(Config) = 0 Then Config = defaultValue
    End Function
    Protected Shared Function ConfigOrEx(ByVal key As String) As String
        Dim s As String = Config(key)
        If Len(s) = 0 Then Throw New Exception("Missing Config setting: " & key)
        Return s
    End Function
    Private Shared m_wrote As New Dictionary(Of String, String)

    Protected Shared Function ConfigIntOrWrite(ByVal key As String, defaultValue As Integer) As Integer
        Dim s As String = String.Empty
        If defaultValue <> Integer.MinValue Then s = defaultValue.ToString
        s = ConfigOrWrite(key, s)
        Dim i As Integer
        If Not Integer.TryParse(s, i) Then Return defaultValue
        Return i
    End Function
    Protected Shared Function ConfigOrWrite(ByVal key As String, defaultValue As String) As String
        Dim s As String = Config(key)
        If Not String.IsNullOrEmpty(s) Then Return s

        If m_wrote.TryGetValue(key, s) Then Return s

        s = defaultValue
        WriteAppSetting(key, defaultValue)

        Return s
    End Function
    Protected Shared Function ConfigBool(ByVal key As String) As Boolean
        Return ConfigBool(key, False)
    End Function
    Protected Shared Function ConfigBool(ByVal key As String, ByVal defaultValue As Boolean) As Boolean
        Dim s As String = Config(key)
        If Len(s) = 0 Then Return defaultValue
        If 0 = String.Compare("true", s, True) Then Return True
        If 0 = String.Compare("yes", s, True) Then Return True
        Return False
    End Function
    Protected Shared Function ConfigInt(ByVal key As String) As Integer
        Return ConfigInt(key, Integer.MinValue)
    End Function
    Protected Shared Function ConfigInt(ByVal key As String, ByVal defaultValue As Integer) As Integer
        Dim s As String = Config(key)
        Dim i As Integer
        If Not Integer.TryParse(s, i) Then Return defaultValue
        Return i
    End Function
    Protected Shared Function ConfigLong(ByVal key As String) As Long
        Return ConfigLong(key, Long.MinValue)
    End Function
    Protected Shared Function ConfigLong(ByVal key As String, ByVal defaultValue As Long) As Long
        Dim s As String = Config(key)
        Dim i As Long
        If Not Long.TryParse(s, i) Then Return defaultValue
        Return i
    End Function
    Protected Shared Function ConfigGuid(ByVal key As String) As Guid
        Return ConfigGuid(key, Guid.Empty)
    End Function
    Protected Shared Function ConfigGuid(ByVal key As String, ByVal defaultValue As Guid) As Guid
        Dim s As String = Config(key)
        If s.Length = 36 OrElse s.Length = 38 Then
            Try
                Return New Guid(s)
            Catch
                Return defaultValue
            End Try
        End If
        Return defaultValue
    End Function


    '        <section name="encryptedSettings" type="System.Configuration.NameValueSectionHandler"/>
    '    </configSections>
    '   <encryptedSettings>
    '    <add key="EncryptionKey" value="asdfasdf" />
    '   </encryptedSettings>
    Private Shared m_checked As Boolean = False
    Private Shared m_hasEncr As Boolean = True
    Private Shared Function EncryptedSettings(ByVal key As String) As String
        If Not m_hasEncr Then Return Nothing
        Dim e As IEnumerable = CType(ConfigurationManager.GetSection("encryptedSettings"), IEnumerable)
        If IsNothing(e) Then
            m_hasEncr = False
            Return Nothing 'Section not found
        End If

        EncryptedSettings = CStr(e.GetType().GetProperties()(0).GetGetMethod().Invoke(e, New Object() {key}))
        If IsNothing(EncryptedSettings) Then Return Nothing 'Key not found

        If m_checked Then Exit Function

        If IsNothing(Current) Then
            CheckConfigIsEncrypted_NonWeb()
        Else
            CheckConfigIsEncrypted_Web()
        End If

        m_checked = True
    End Function
    Private Shared Sub CheckConfigIsEncrypted_Web()
        Try
            If IsNothing(Current.Request) Then Exit Sub
        Catch
            Exit Sub
        End Try
        If Current.Request.Url.Host.ToLower().Contains("localhost") Then Exit Sub
        Dim c As Configuration
        Try
            c = WebConfigurationManager.OpenWebConfiguration(Current.Request.ApplicationPath)
        Catch ex As Exception
            c = WebConfigurationManager.OpenWebConfiguration("~/web.config")
        End Try
        Dim cs As ConfigurationSection = c.GetSection("encryptedSettings")
        If IsNothing(cs) Then Exit Sub
        Dim si As SectionInformation = cs.SectionInformation
        If Not si.IsProtected Then
            si.ProtectSection("DataProtectionConfigurationProvider") 'RSA '"DPAPIProtectedConfigurationProvider")
            'c.SaveAs("d:\\test.txt", ConfigurationSaveMode.Full)
            Try
                c.Save(ConfigurationSaveMode.Full)
            Catch ex As Exception
                Dim path As String = Current.Server.MapPath("~/App_Data/web.config.encrypted.txt")
                Try
                    c.SaveAs(path, ConfigurationSaveMode.Full)
                Catch
                    Throw New Exception("Insufficient permissions to update web.config, or save it to ~/App_Data. Need to configure permissions to allow save")
                End Try
                Throw New Exception("Insufficient permissions to update web.config, so wrote encrypted config section to: " & path)
            End Try
        End If
    End Sub
    Private Shared Sub CheckConfigIsEncrypted_NonWeb()
        Dim c As Configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
        Dim cs As ConfigurationSection = c.GetSection("encryptedSettings")
        If IsNothing(cs) Then Exit Sub
        Dim si As SectionInformation = cs.SectionInformation
        If Not si.IsProtected Then
            si.ProtectSection("DataProtectionConfigurationProvider") 'RSA '"DPAPIProtectedConfigurationProvider")
            c.Save(ConfigurationSaveMode.Full)
        End If
    End Sub

End Class
